#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import typing
from enum import Enum

import httpx


class YT(Enum):
    """ yang模型的数据类型 """
    NONE = ''
    ALL = 'all'
    CONFIG = 'config'
    NON_CONFIG = 'nonconfig'


class ODLConfig:
    """ OpenDayLight的服务器REST连接参数 """

    def __init__(self, host='127.0.0.1', port=8181, username='admin', password='admin'):
        self.Host = host
        self.Port = port
        self.Username = username
        self.Password = password


class NodeConfig:
    """ 交换机设备的 NETCONF 接口连接参数 """

    def __init__(self, host='192.168.10.251', port=830, username='root', password='123'):
        self.Host = host
        self.Port = port
        self.Username = username
        self.Password = password


class ODLHandler:
    """ 操作OpenDayLight的REST接口来配置NETCONF交换机的配置 """

    def __init__(self, odl: ODLConfig, node: NodeConfig):
        self.odl = odl
        self.node = node

        # 初始化 httpx 客户端
        base_url = f'http://{odl.Host}:{odl.Port}/rests/data/network-topology:network-topology/topology=topology-netconf'
        headers = {'Content-Type': 'application/yang-data+json', 'Accept': 'application/yang-data+json, */*;q=0.5'}
        self.client = httpx.Client(auth=(odl.Username, odl.Password), base_url=base_url, headers=headers)

    def create_connector(self) -> bool:
        """ 在 OpenDaylight 中，为该主机创建 NETCONF 连接器 """
        json_data = {"node": [{"node-id": self.node.Host, "netconf-node-topology:port": 830,
                               "netconf-node-topology:reconnect-on-changed-schema": False,
                               "netconf-node-topology:connection-timeout-millis": 20000,
                               "netconf-node-topology:tcp-only": False,
                               "netconf-node-topology:max-connection-attempts": 0,
                               "netconf-node-topology:login-password-unencrypted": {
                                   "netconf-node-topology:username": self.node.Username,
                                   "netconf-node-topology:password": self.node.Password, },
                               "netconf-node-topology:host": self.node.Host,
                               "netconf-node-topology:keepalive-delay": 120}]}
        # put方法如果对象不存在则创建，如果已经存在则修改
        resp = self.client.put(f'/node={self.node.Host}', json=json_data)
        return resp.status_code == 201 or resp.status_code == 204

    def delete_connector(self) -> bool:
        """ 删除 NETCONF 连接器 """
        resp = self.client.delete(f'/node={self.node.Host}')
        # 忽略409错误。如果删除的连接器不存在，则会返回409错误
        return resp.status_code == 204 or resp.status_code == 409

    def check_connector_connection_status(self) -> bool:
        """ 检查连接器的连接状态，如果是connected则返回True，否则返回False """
        resp = self.client.get(f'/node={self.node.Host}')
        if resp.status_code == 200:
            data = resp.json()
            nodes = data['network-topology:node']
            return len(nodes) == 1 and nodes[0].get('netconf-node-topology:connection-status') == 'connected'
        elif resp.status_code == 409:  # 如果连接器不存在，则返回False
            raise FileNotFoundError('连接器不存在')
        elif resp.status_code == 401:
            raise PermissionError('服务器拒绝访问')
        return False

    def get(self, yang_pth='', data_type=YT.CONFIG, fields='') -> any:
        """ 获取设备的配置，不提供参数将获取所有数据

         :param yang_pth: 指定Yang路径，例如readylinks-rlos:configure, readylinks-rlos:configure/bandwidth-profiles/bandwidth-profile=1
         :param data_type: 内容类型，默认是YangType.ALL
         :param fields: 只显示哪些字段，多个字段用分号隔开，只有当数据结果是一个字典(不是列表)的时候才有效
        """
        url = f'/node={self.node.Host}/yang-ext:mount'
        if yang_pth != '':
            url = f'{url}/{yang_pth}'
        if data_type != YT.NONE:
            url = f'{url}?content={data_type.value}'
        if fields != '':
            url = f'{url}&fields={fields}'

        resp = self.client.get(url)
        if resp.status_code == 200:
            return resp.json()

    def _edit_config(self, yang_pth: str, json: typing.Any = None, method: str = 'put') -> int:
        """ 编辑指定的配置

          :param yang_pth: 指定Yang路径，例如readylinks-rlos:configure, readylinks-rlos:configure/bandwidth-profiles/bandwidth-profile=1
          :param json: 提交的 json 对象
          :param method: http 方法
        """
        method = method.upper()
        timeout = 10

        url = f'/node={self.node.Host}/yang-ext:mount/{yang_pth}'
        if method == 'PUT':
            resp = self.client.put(url, json=json, timeout=timeout)
        elif method == 'POST':
            resp = self.client.post(url, json=json, timeout=timeout)
        elif method == 'DELETE':
            resp = self.client.delete(url, timeout=timeout)
        else:
            raise NotImplementedError

        return resp.status_code

    def post(self, yang_pth: str, json: typing.Any) -> bool:
        """ 创建配置 """
        code = self._edit_config(yang_pth=yang_pth, json=json, method='POST')
        return code == 201

    def put(self, yang_pth: str, json: typing.Any) -> bool:
        """ 创建或更新配置 """
        code = self._edit_config(yang_pth=yang_pth, json=json, method='PUT')
        return code == 201 or code == 204

    def delete(self, yang_pth: str) -> bool:
        """ 删除指定的配置 """
        code = self._edit_config(yang_pth=yang_pth, method='DELETE')
        return code == 204
